package no.ntnu.fp.su;

import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JOptionPane;

import no.ntnu.fp.db.Database;
import no.ntnu.fp.mmi.MasterWatchModel;
import no.ntnu.fp.mmi.MasterWatchPanel;
import no.ntnu.fp.net.co.Connection;
import no.ntnu.fp.net.co.ConnectionImpl;
import no.ntnu.fp.net.co.ReceiveConnectionWorker;
import no.ntnu.fp.net.co.ReceiveMessageWorker;
import no.ntnu.fp.net.co.ReceiveConnectionWorker.ConnectionListener;
import no.ntnu.fp.net.co.ReceiveMessageWorker.MessageListener;

/**
 * Main object for MASTER application
 * 
 * 
 */
public class Master extends Thread {

	// private MasterPanel guiModel;
	private ArrayList<Connection> inConnections;
	private ArrayList<Connection> outConnections;
	private Connection connectionListener;
	private ArrayList<Platform> platforms;
	private Boolean platformWatchRunning;
	private MasterWatchPanel mwp;
	
	

	/**
	 * 
	 * @return {@link Boolean} true if a watch window is open, else false
	 */
	public synchronized Boolean getPlatformWatchRunning() {
		return platformWatchRunning;
	}

	/**
	 * set if a watch window is running
	 * 
	 * @param {@link Boolean}
	 */
	public synchronized void setPlatformWatchRunning(
			Boolean platformWatchRunning) {
		this.platformWatchRunning = platformWatchRunning;
	}

	private ArrayList<Message> messages;

	/**
	 * Get messages
	 * 
	 * @return {@link ArrayList<Message>
	 */
	public ArrayList<Message> getMessages() {
		return messages;
	}

	private Events events;
	private Map<Platform, Connection> connectionOfIns = Collections
			.synchronizedMap(new HashMap<Platform, Connection>());
	private Map<Platform, Connection> connectionOfOuts = Collections
			.synchronizedMap(new HashMap<Platform, Connection>());

	private ArrayList<ReceiveMessageWorker> receiveMessageWorkers;
	private ReceiveConnectionWorker rcw;

	private Database db;

	private boolean isRunning;
//	private MasterWatchModel mwm;

	/**
	 * Constructor
	 */
	public Master() {
		connectionListener = new ConnectionImpl(10500);
		inConnections = new ArrayList<Connection>();
		outConnections = new ArrayList<Connection>();
		platforms = new ArrayList<Platform>();
		platformWatchRunning = new Boolean(false);
		messages = new ArrayList<Message>();
		receiveMessageWorkers = new ArrayList<ReceiveMessageWorker>();

		rcw = new ReceiveConnectionWorker(connectionListener,
				new ConnectionWorker(this));
		db = new Database();
		events = new Events(0, db);
		events.getEventsFromDB();

		loadPlatforms();

		// startConnectionLister cl = new startConnectionLister();
		// cl.start();

	}

	/**
	 * Entry point for Master
	 * 
	 */
	public void run() {
		isRunning = true;

		rcw.start();

		System.out.println("Starting Platforms");
		for (Platform p : platforms) {
			p.start();
		}

		System.out.println("lager mwm");
		new MasterWatchModel(this);
		System.out.println("mwm laget");

		while (isRunning) {
			try {
				sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Get the {@link Platform}s
	 * 
	 * @return {@link ArrayList<Platform>}
	 */
	public ArrayList<Platform> getPlatforms() {
		return platforms;
	}

	/**
	 * Get {@link Events}
	 * 
	 * @return {@link Events}
	 */
	public Events getEvents() {
		return events;
	}

	/**
	 * Stops the MASTER thread, Disconnects all connections, and stop all
	 * threads
	 */
	public synchronized void stopRunning() {
		rcw.stopRunning();

		for (Connection c : inConnections) {
			try {
				c.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		isRunning = false;
	}

	/**
	 * Send a {@link Message} to the {@link Platform} after construction and
	 * parsing
	 * 
	 * @param {@link String} message to send
	 * @param {@link Platform} Platform to send the message to
	 */
	public boolean sendMessage(String s, Platform plat) {
		Message message = new Message();
		message.setText(s);
		message.setTimestamp(new Timestamp(System.currentTimeMillis()));
		message.setFrom(null); // null = master
		return sendToPlatform(message, plat);
	}

	/**
	 * Will send the message
	 * 
	 * @see Master#sendMessage(String, Platform)
	 */
	private boolean sendToPlatform(Message message, Platform platform) {
		try {
			connectionOfOuts.get(platform).send(message.toString());
		} catch (ConnectException e) {
			System.out.println("not connected, can't send");
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			System.out.println("IO error, can't send");
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * parse message headers on a received {@link Message}
	 * 
	 * @param {@link String} - packetBody - the string packet from the
	 *        Connection
	 * @return {@link Message} the Message Object which was received
	 */
	public Message decodeMessage(String packetBody) {
		// decomp[] -> "message" - <from> - <timestamp(milliseconds)> -
		// <the message>
		String[] decomp = packetBody.split(Watch.SEPARATOR);
		Message msg = new Message();
		msg.setFrom(platIDToPlat(Integer.parseInt(decomp[1])));
		msg.setTimestamp(new Timestamp(Long.parseLong(decomp[2])));
		msg.setText(decomp[3]);
		return msg;
	}

	/**
	 * 
	 * 
	 * @param {@link Integer} Platform ID
	 * @return the Platform represented by the platformID string
	 */
	private Platform platIDToPlat(int id) {
		for (Platform p : platforms)
			if (id == p.getID())
				return p;
		return null;
	}

	/**
	 * Leser inn platform data fra db, og lager platformene
	 */
	public void loadPlatforms() {
		Database db = new Database();
		ResultSet rs;

		String navn;
		int id;

		db.connect();
		rs = db.query("SELECT * FROM platform");
		try {
			while (rs.next()) {
				id = rs.getInt(1);
				navn = rs.getString(2);

				platforms.add(new Platform(id, navn, false));

			}
		} catch (SQLException e) {
			e.printStackTrace();
		}

	}

	/**
	 * remove closed down connections.
	 * 
	 */
	private void cleanupConnections() {
		for (Connection con : inConnections) {
			if (!((ConnectionImpl) con).isEstablished()) {
				System.out.println(con.toString() + " connection removed");
				inConnections.remove(con);
			}
		}

	}

	/**
	 * Add message to platform list
	 * 
	 * @param {@link Message} msg
	 */
	public void addMessage(Message msg) {
		messages.add(msg);
		mwp.refreshMSG();
		JOptionPane.showMessageDialog(null, "Message From : " + msg.getFrom() + "/n" + msg.getText());
		
		// TODO notify


	}

	/**
	 * Connecter til en watch
	 * 
	 * @param connectIP
	 *            - IP adressen til watch
	 * @param connectPort
	 *            - Port til watch
	 * @param plat
	 *            - hvilken platform har watch
	 */
	public void connectToWatch(String connectIP, int connectPort, Platform plat) {
		Connection con = new ConnectionImpl();
		try {
			con.connect(connectIP, connectPort);
		} catch (SocketTimeoutException e) {
			// TODO Retry transmitt
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Retry transmit
			e.printStackTrace();
		}
		outConnections.add(con);
		connectionOfOuts.put(plat, con);

	}

	/**
	 * Open Platform window
	 * 
	 * @param {@link Platform}
	 */
	public void gotoPlatform(Platform plat) {
		if (!platformWatchRunning) {
			Watch watch = new Watch(plat, connectionOfIns.get(plat),
					connectionOfOuts.get(plat), this);
			watch.start();
			// platformWatchRunning = true;
		}
	}

	/**
	 * Test, debug only
	 * 
	 * @param args
	 * @deprecated
	 */
	public static void main(String[] args) {
		Master mainM;
		System.out.println("launching Master");
		mainM = new Master();
		mainM.start();

		System.out.println("Main done");
	}

	/**
	 * Helpclass for incomming connections
	 * 
	 * @author stn
	 * 
	 */
	class ConnectionWorker implements ConnectionListener {

		private Master master;

		/**
		 * Constructor
		 * 
		 * @param {@link Master} - parent
		 */
		public ConnectionWorker(Master parent) {
			master = parent;
		}

		/**
		 * Method that is called when a new connection is received add
		 * connection to {@see Master#inConnections} and start message worker
		 */
		@Override
		public void connectionReceived(Connection connection) {

			master.inConnections.add(connection);
			ReceiveMessageWorker rmw = new ReceiveMessageWorker(connection);
			receiveMessageWorkers.add(rmw);
			rmw.addMessageListener(new MessageWorker(connection, master));
			rmw.start();

		}

	}

	/**
	 * Helpclass for receiving messages through the connection
	 * 
	 * 
	 */
	class MessageWorker implements MessageListener {

//		private Connection con;
		private Master master;

		/**
		 * Constructor
		 * 
		 * @param {@link Connection} - Connection to listen to
		 * @param {@link Master}�- parent
		 */
		public MessageWorker(Connection con, Master parent) {
//			this.con = con;
			master = parent;
		}

		/**
		 * Method that is called when the connection is closed
		 */
		@Override
		public void connectionClosed(Connection conn) {
			master.cleanupConnections();
		}

		/**
		 * Method that is called when a datapacket is received
		 */
		@Override
		public void messageReceived(String message) {
			System.out.println("Message received = " + message);
			if (message == null)
				return;
			String[] msg = message.split(Watch.SEPARATOR);
			System.out.println("msg størelse " + msg.length + "  " + msg[0]);
			if (msg[0].equals("Platform")) {
				int id = Integer.parseInt(msg[1]);

				Platform plat = platIDToPlat(id);
				// connectionOfIns.put(plat, con);

				String connectIP = msg[3];
				int connectPort = Integer.parseInt(msg[4]);

				System.out.println("Platform " + msg[2] + " " + msg[3] + ":"
						+ msg[4]);

				master.connectToWatch(connectIP, connectPort, plat);

			} else if (msg[0] == "message") {
				System.out.println("IKKE PLATFORM: " + message);
				addMessage(decodeMessage(message));

			}

		}

	}

}
